/*
 * $Id: AlbumSongList.java 372 2008-04-14 23:26:48Z jasta $
 *
 * Copyright (C) 2008 Josh Guilfoyle <jasta@devtcg.org>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2, or (at your option) any
 * later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 */

package org.devtcg.five.music.activity;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.devtcg.five.music.widget.ProgressTextView;
import org.devtcg.five.music.R;
import org.devtcg.five.music.service.IPlaylistBufferListener;
import org.devtcg.five.music.service.IPlaylistMoveListener;
import org.devtcg.five.music.service.IPlaylistService;
import org.devtcg.five.music.util.PlaylistUtil;
import org.devtcg.five.provider.Five;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.database.ContentObserver;
import android.database.Cursor;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.net.Uri;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.Menu.Item;
import android.view.View.OnPopulateContextMenuListener;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ViewSwitcher;
import android.widget.AdapterView.ContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;

public class AlbumSongList extends PlayNotifActivity
{
	private static final String TAG = "AlbumSongList";

	private static final String[] PROJECTION_ALBUM = {
		"_id", "full_name", "artwork"
	};

	private static final String[] PROJECTION_SONGS = {
		"_id", "title", "length", "track_num", "content_id"
	};

	private ListView mList;
	private ProgressTextView mBufferBar;

	private Cursor mCursorAlbum;
	private Cursor mCursorSongs;

	/* Set of queued tracks from the current view. */
	private Map<Long, Integer> mQueued =
	  Collections.synchronizedMap(new HashMap<Long, Integer>());

	/* Set of song identifiers currently displayed here.  Used to efficiently
	 * determine whether any song on screen is currently being played,
	 * so that the UI can react appropriately. */
	private Set<Long> mSongs =
	  Collections.synchronizedSet(new HashSet<Long>());

	private long mSongPlaying = -1;

	private IPlaylistService mService;

	private static final int MSG_BUFFER_UPDATE = 0;
	private static final int MSG_START = 1;
	private static final int MSG_CHOKE = 2;
	
	private static final int MENU_ENQUEUE_LAST = Menu.FIRST;
	private static final int MENU_PLAY_NEXT = Menu.FIRST + 1;
	private static final int MENU_PLAY_NOW = Menu.FIRST + 2;
	private static final int MENU_PAUSE = Menu.FIRST + 3;
	private static final int MENU_REPEAT = Menu.FIRST + 4;
	private static final int MENU_STOP = Menu.FIRST + 5;

    @Override
    public void onCreate(Bundle icicle)
    {
    	super.onCreate(icicle);
    	requestWindowFeature(Window.FEATURE_NO_TITLE);
    	setContentView(R.layout.album_song_list);

    	Uri album = getIntent().getData();

    	mCursorAlbum = managedQuery(album, PROJECTION_ALBUM, null, null, null);
    	setAlbumText();

        mCursorAlbum.registerContentObserver(new ContentObserver(new Handler()) {
        	public void onChange(boolean selfChange)
        	{
        		assert selfChange == false : "Is someone using our cursor other than us?";
        		setAlbumText();
        	}
        });

    	Uri songs = album.buildUpon()
    	  .appendPath("songs")
    	  .build();

    	mCursorSongs = managedQuery(songs, PROJECTION_SONGS, null, null, "track_num ASC, title ASC");

    	if (mCursorSongs.count() == 0)
    		finish();

        mList = (ListView)findViewById(android.R.id.list);

        /* TODO: This is duplicated in AlbumAndSongList.  Centralize somewhere
         * once the UI stabilizes. */
        SimpleCursorAdapter adapter = new SongListAdapter(this,
          R.layout.song_list_item,
          mCursorSongs,
          new String[] { "track_num", "title", "length" },
          new int[] { R.id.left_column, R.id.song_name, R.id.right_column }
        );

		adapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
			public boolean setViewValue(View v, Cursor c, int col)
			{
				if (col == c.getColumnIndex("track_num"))
				{
					ViewSwitcher vv = (ViewSwitcher)v;
					int curr = vv.getDisplayedChild();
					
					if (mSongPlaying >= 0 && c.getLong(0) == mSongPlaying)
					{
						if (curr == 0)
							vv.setDisplayedChild(1);

						/* TODO: This seems very inefficient.  Consider a
						 * different approach. */
						mBufferBar = (ProgressTextView)((ViewGroup)v.getParent())
						  .findViewById(R.id.song_name);
					}
					else
					{
						if (curr == 1)
							vv.setDisplayedChild(0);

						String trk = c.getString(col);

						if (trk != null)
						{
							if (trk.length() == 1)
								trk = "0" + trk;

							trk += '.';

							((TextView)vv.getCurrentView()).setText(trk);
						}
					}
				}
				else if (col == c.getColumnIndex("length"))
				{
					ViewSwitcher vv = (ViewSwitcher)v;
					int curr = vv.getDisplayedChild();

					if (mQueued.isEmpty() == false &&
					    mQueued.containsKey(c.getLong(0)) == true)
					{
						if (curr == 0)
							vv.setDisplayedChild(1);

						((TextView)vv.getCurrentView())
						  .setText(String.valueOf(mQueued.get(c.getLong(0))));
					}
					else
					{
						if (curr == 1)
							vv.setDisplayedChild(0);

						int seconds = c.getInt(col);
						int minutes = seconds / 60;
						seconds = seconds % 60;

						StringBuilder b = new StringBuilder();
						b.append('(');
						b.append(minutes);
						b.append(':');

						if (seconds < 10)
							b.append('0');

						b.append(seconds);
						b.append(')');

						((TextView)vv.getCurrentView()).setText(b.toString());
					}
				}
				else
				{
					return false;
				}

				return true;
			}
		});

		mList.setOnItemClickListener(mOnItemClickListener);
		mList.setOnPopulateContextMenuListener(mOnPopulateContextMenuListener);
        mList.setAdapter(adapter);

        buildSongsMap();

        bindService();
    }

    private void buildSongsMap()
    {
     	mCursorSongs.moveTo(-1);

    	while (mCursorSongs.next() == true)
    		mSongs.add(mCursorSongs.getLong(0));
    }

    @Override
    public void onDestroy()
    {
    	unbindService();
    	super.onDestroy();
    }

    private void setAlbumText()
    {
    	if (mCursorAlbum.count() == 0)
    		finish();

        mCursorAlbum.first();
        ((TextView)findViewById(R.id.album_name)).setText(mCursorAlbum.getString(1));

        String artwork = mCursorAlbum.getString(2);

        ImageView artworkView = (ImageView)findViewById(R.id.album_cover);

        if (artwork != null)
        	artworkView.setImageURI(Uri.parse(artwork));
        else
        	artworkView.setImageResource(R.drawable.lastfm_cover_small);
    }

    private void bindService()
    {
    	PlaylistUtil.bindService(this, mConnection);
    }

    private void unbindService()
    {
    	if (mService != null)
    	{
    		try { mService.unregisterOnMoveListener(mPlaylistMoveListener); }
    		catch (DeadObjectException e) {} 
    	}

    	unbindService(mConnection);
    }

    private Handler mHandler = new Handler()
    {
    	public void handleMessage(Message msg)
    	{
    		switch (msg.what)
    		{
    		case MSG_BUFFER_UPDATE:
    			if (msg.arg1 == mSongPlaying)
    			{
    				if (mBufferBar != null)
    				{
    					if (msg.arg2 < 100)
    						mBufferBar.setProgress(msg.arg2);
    				}
    			}

    			break;
    		case MSG_CHOKE:
    			Log.d(TAG, "MSG_CHOKE: songId=" + msg.arg1);
    			break;
    		case MSG_START:
    			Log.d(TAG, "MSG_START: songId=" + msg.arg1);

    			if (mBufferBar != null)
    			{
    				Log.d(TAG, "Hiding buffer bar...");

//    				mBufferBar.startAnimation(AnimationUtils.loadAnimation(AlbumSongList.this,
//    				  android.R.anim.fade_out));
    				mBufferBar.drainProgress();
    			}

    			break;
    		default:
    			super.handleMessage(msg);
    		}
    	}
    };

    private void populateQueue()
    {
    	assert mService != null;

    	try { populateQueue(mService.getPosition()); }
    	catch (DeadObjectException e) { finish(); }
    }

    private void populateQueue(int pos)
    {
    	assert mService != null;

    	mQueued.clear();

    	try
    	{
    		if (mService.isPlaying() == false)
    			return;

    		assert pos >= 0;

    		List playlist = mService.getPlaylistWindow(pos + 1, pos + 4);
    		
    		if (playlist == null)
    			return;
    		
    		int n = playlist.size();

    		for (int i = 0; i < n; i++)
    		{
    			long id = (Long)playlist.get(i);
    			
    			if (mSongs.contains(id) == true)
    				mQueued.put(id, i + 1);
    		}
    	}
    	catch (DeadObjectException e)
    	{
    		finish();
    	}
    }

    private final ServiceConnection mConnection = new ServiceConnection()
    {
		public void onServiceConnected(ComponentName name, IBinder service)
		{
			mService = IPlaylistService.Stub.asInterface(service);

			try
			{
				int pos = mService.getPosition();
				Log.d(TAG, "pos=" + pos);

				final long songId = mService.getSongAt(pos);
				Log.d(TAG, "songId=" + songId);

				if (pos >= 0)
					populateQueue(pos);

				mHandler.post(new Runnable() {
					public void run()
					{
						boolean invalidate = false;

						if (mSongPlaying != songId)
						{
							mSongPlaying = songId;
							invalidate = true;
						}
						else if (mQueued.size() > 0)
						{
							invalidate = true;
						}

						if (invalidate == true)
							mList.invalidateViews();
					}
				});
				
				mService.registerOnMoveListener(mPlaylistMoveListener);
			}
			catch (DeadObjectException e)
			{
				Toast.makeText(AlbumSongList.this, "BUG: " + e.toString(),
				  Toast.LENGTH_SHORT).show();
				Log.d(TAG, "BUG", e);
			}
		}

		public void onServiceDisconnected(ComponentName name)
		{
			mService = null;
		}
    };

    private IPlaylistMoveListener.Stub mPlaylistMoveListener = new IPlaylistMoveListener.Stub()
    {
    	private final Runnable mUpdateQueue = new Runnable()
    	{
    		public void run()
    		{
    			populateQueue();
    			mList.invalidateViews();
    		}
    	};
    	
		public void advance() throws DeadObjectException
		{
			long songId = mService.getSongAt(mService.getPosition());
			reactIfPlaying(songId);
			mHandler.post(mUpdateQueue);
		}

		public void jump(int pos) throws DeadObjectException
		{
			reactIfPlaying(mService.getSongAt(pos));			
			mHandler.post(mUpdateQueue);
		}

		public void pause()
		{
		}

		public void play() throws DeadObjectException
		{
			reactIfPlaying(mService.getSongAt(mService.getPosition()));
		}

		public void seek(long pos)
		{
		}

		public void skip(long offs)
		{
		}

		public void stop()
		{
			reactIfPlaying(-1);
		}
    };

    private IPlaylistBufferListener.Stub mPlaylistBufferListener = new IPlaylistBufferListener.Stub()
    {
		public void onBufferUpdate(long songId, int percent, int bufferPercent)
		  throws DeadObjectException
		{
			/* XXX: Should not cast songId to int here! */
			Message msg = mHandler.obtainMessage(MSG_BUFFER_UPDATE, (int)songId, bufferPercent);
			mHandler.sendMessage(msg);
		}

		public void onStreamChoke(long songId) throws DeadObjectException
		{
			Message msg = mHandler.obtainMessage(MSG_CHOKE, (int)songId, 0);
			mHandler.sendMessage(msg);
		}

		public void onStreamStart(long songId) throws DeadObjectException
		{
			Message msg = mHandler.obtainMessage(MSG_START, (int)songId, 0);
			mHandler.sendMessage(msg);
		}
    };

    private void reactIfPlaying(final long songId)
    {
    	Log.d(TAG, "reactIfPlaying, songId=" + songId);

    	boolean mySong = mSongs.contains(songId);

    	try
    	{
    		if (mySong == true)
    			mService.registerOnBufferListener(mPlaylistBufferListener);
    		else
    			mService.unregisterOnBufferListener(mPlaylistBufferListener);
    	}
    	catch (DeadObjectException e)
    	{
    		Toast.makeText(this, "BUG: " + e.toString(),
    		  Toast.LENGTH_SHORT).show();
    		Log.d(TAG, "BUG", e);
    	}

    	if (songId == -1 || mySong == true)
    	{
    		mHandler.post(new Runnable() {
    			public void run()
    			{
					if (mSongPlaying != songId)
					{
						mSongPlaying = songId;
						mList.invalidateViews();
					}
    			}
    		});
    	}
    	else if (mySong == false)
    	{
    		mHandler.post(new Runnable() {
    			public void run()
    			{
    				mSongPlaying = -1;
    				mList.invalidateViews();
    			}
    		});
    	}
    }
    
    @Override
    public boolean onContextItemSelected(Item item)
    {
    	boolean ret = true;
    	ContextMenuInfo info = (ContextMenuInfo)item.getMenuInfo();
    	long songId = info.id;

    	try 
    	{
    		switch (item.getId())
    		{
    		case MENU_STOP:
    			mService.stop();
    			break;
    		case MENU_PAUSE:
    			mService.pause();
    			break;
    		case MENU_REPEAT:
    			Toast.makeText(this, "Not supported", Toast.LENGTH_SHORT).show();
    			break;

    		case MENU_PLAY_NEXT:
    			mService.enqueueNext(songId);
    			populateQueue();
    			mList.invalidateViews();
    			break;
    		case MENU_PLAY_NOW:
    			mService.enqueueAndPlay(songId);
    			break;
    		case MENU_ENQUEUE_LAST:
    			mService.enqueue(songId);
    			populateQueue();
    			mList.invalidateViews();
    			break;
    		default:
    			ret = false;
    			break;
    		}
    	}
    	catch (DeadObjectException e)
    	{
    		finish();
    	}

    	return ret;
    }
    
    private final OnPopulateContextMenuListener mOnPopulateContextMenuListener = new OnPopulateContextMenuListener()
    {
    	public void onPopulateContextMenu(ContextMenu menu, View v, Object menuInfo)
    	{
    		ContextMenuInfo info = (ContextMenuInfo)menuInfo;
    		
    		if (mSongPlaying == info.id)
    		{
    			menu.add(0, MENU_STOP, "Stop");
    			menu.add(0, MENU_PAUSE, "Pause");
    			menu.add(0, MENU_REPEAT, "Repeat once");
    		}
//    		else if (mQueued.get(info.id) == true)
//    		{
//    			menu.add(0, MENU_JUMP_TO, "Play now");
//    			menu.add(...
//    		}
    		else
    		{
    			menu.add(0, MENU_PLAY_NEXT, "Play next");
    			menu.add(0, MENU_PLAY_NOW, "Play now");
    			menu.add(0, MENU_ENQUEUE_LAST, "Enqueue");
    		}
    	}
    };

    private OnItemClickListener mOnItemClickListener = new OnItemClickListener()
    {
		public void onItemClick(AdapterView av, View v, int pos, long id)
		{
			try
			{
				int p = mService.enqueueAndPlay(id);
			}
			catch (Exception e)
			{
				Toast.makeText(AlbumSongList.this, "BUG: " + e.toString(),
				  Toast.LENGTH_LONG).show();

				Log.d(TAG, "Shit", e);
			}
		}
    };

    private static class SongListAdapter extends SimpleCursorAdapter
    {
		public SongListAdapter(Context context, int layout, Cursor c,
		  String[] from, int[] to)
		{
			super(context, layout, c, from, to);
		}
		
		@Override
		public View newView(Context ctx, Cursor c, ViewGroup parent)
		{
			View v = super.newView(ctx, c, parent);
			
//			((ViewGroup)v).setAnimationCacheEnabled(false);
//
//			ViewSwitcher vv = (ViewSwitcher)v.findViewById(R.id.left_column);
//			vv.setInAnimation(AnimationUtils.loadAnimation(ctx, android.R.anim.fade_in));
//			vv.setOutAnimation(AnimationUtils.loadAnimation(ctx, android.R.anim.fade_out));

//			View slider = v.findViewById(R.id.queued_slider);
//			
//			float[] corners = { 20F, 20F, 0F, 0F, 0F, 0F, 20F, 20F };
//			RoundRectShape shape = new RoundRectShape(corners, null, null);
//			ShapeDrawable drawable = new ShapeDrawable(shape);
//			drawable.setColor(0xfffd8d30);
//			slider.setBackground(drawable);

			return v;
		}
    }
}
